<?php

declare(strict_types=1);

namespace core;

class App
{
    /** DI 容器实例 */
    private Container $container;

    /** 应用配置 */
    private array $config;

    /** 控制器类名 */
    private string $controller = '';

    /** 方法名 */
    private string $method = '';

    /** URL参数数组 */
    private array $params = [];

    /** 请求 URI（用于测试） */
    private string $uri;

    /**
     * 构造函数
     * @param array $config 应用配置
     * @param string|null $uri 用于测试的 URI
     */
    public function __construct(array $config, ?string $uri = null)
    {
        $this->config = $config;
        $this->uri = $uri ?? ($_SERVER['REQUEST_URI'] ?? '/');
        $this->container = new Container();
    }

    /**
     * 应用运行入口
     */
    public function run(): void
    {
        try {
            $this->parseUrl();
            $this->dispatch();
        } catch (\Throwable $th) {
            $this->handleError($th);
        }
    }

    /**
     * 解析 URL 路径
     * @throws \InvalidArgumentException
     */
    private function parseUrl(): void
    {
        $uri = rawurldecode($this->uri);

        // URI 长度限制
        if (mb_strlen($uri, '8bit') > 1024) {
            throw new \InvalidArgumentException('URI too long');
        }

        // 防御空字节和路径穿越
        if (
            str_contains($uri, "\0") ||
            preg_match('/\.\.(\\\|\/|$)/', $uri) !== 0 ||
            str_contains($uri, '%2e%2e')
        ) {
            throw new \InvalidArgumentException('Invalid URI: contains illegal characters');
        }

        $path = trim(parse_url($uri, PHP_URL_PATH) ?: '/', '/');
        $segments = $path === '' ? [] : explode('/', $path);

        $this->validateAndSetRoute($segments);
    }

    /**
     * 验证并设置路由参数
     * @param array $segments URL分段数组
     * @throws \InvalidArgumentException
     */
    private function validateAndSetRoute(array $segments): void
    {
        $controllerName = ucfirst($segments[0] ?? $this->config['defaultController']);
        $methodName = $segments[1] ?? $this->config['defaultMethod'];

        if (!$this->isValidIdentifier($controllerName, true)) {
            throw new \InvalidArgumentException("Invalid controller name: {$controllerName}");
        }

        if (!$this->isValidIdentifier($methodName, false)) {
            throw new \InvalidArgumentException("Invalid method name: {$methodName}");
        }

        $this->controller = $this->config['controllerNamespace'] . '\\' . $controllerName;
        $this->method = $methodName;
        $this->params = array_slice($segments, 2);
    }

    /**
     * 验证标识符（控制器/方法）是否合法
     * @param string $identifier 标识符
     * @param bool $isController 是否为控制器
     * @return bool
     */
    private function isValidIdentifier(string $identifier, bool $isController = true): bool
    {
        if (strlen($identifier) > 64) {
            return false;
        }
        $pattern = $isController ? '/^[A-Za-z][A-Za-z0-9_]*$/u' : '/^[A-Za-z_][A-Za-z0-9_]*$/u';
        return preg_match($pattern, $identifier) === 1;
    }

    /**
     * 调度控制器和方法
     * @throws \ReflectionException | \InvalidArgumentException
     */
    private function dispatch(): void
    {
        if (!class_exists($this->controller)) {
            throw new \InvalidArgumentException("Controller class does not exist: {$this->controller}");
        }

        $reflectionClass = new \ReflectionClass($this->controller);
        $allowedBaseClass = 'app\\controller\\BaseController';

        // **安全增强**: 强制控制器继承自指定基类
        if (!$reflectionClass->isSubclassOf($allowedBaseClass)) {
            throw new \InvalidArgumentException("Controller must extend {$allowedBaseClass}");
        }

        // **DI 优化**: 使用容器创建实例
        $controllerObj = $this->container->get($this->controller);

        if (!method_exists($controllerObj, $this->method)) {
            throw new \InvalidArgumentException("Method does not exist: {$this->controller}::{$this->method}");
        }

        $reflectionMethod = new \ReflectionMethod($controllerObj, $this->method);

        if (!$reflectionMethod->isPublic() || str_starts_with($this->method, '__')) {
            throw new \InvalidArgumentException("Method is not accessible: {$this->controller}::{$this->method}");
        }

        $args = $this->resolveMethodArguments($reflectionMethod);
        $reflectionMethod->invokeArgs($controllerObj, $args);
    }

    /**
     * 解析方法参数
     * @param \ReflectionMethod $method
     * @return array
     * @throws \InvalidArgumentException
     */
    private function resolveMethodArguments(\ReflectionMethod $method): array
    {
        $args = [];
        foreach ($method->getParameters() as $index => $parameter) {
            $paramName = $parameter->getName();
            $paramValue = $this->params[$index] ?? null;

            if ($paramValue !== null) {
                $args[] = $this->sanitizeArgument($paramValue, $parameter);
            } elseif ($parameter->isDefaultValueAvailable()) {
                $args[] = $parameter->getDefaultValue();
            } else {
                throw new \InvalidArgumentException("Missing required parameter: {$paramName}");
            }
        }
        return $args;
    }

    /**
     * 清理和类型化参数
     * @param mixed $value
     * @param \ReflectionParameter $parameter
     * @return mixed
     */
    private function sanitizeArgument(mixed $value, \ReflectionParameter $parameter): mixed
    {
        $type = $parameter->getType();
        if ($type === null) {
            return is_string($value) ? $this->removeControlChars($value) : $value;
        }

        $typeName = $type->getName();
        return match ($typeName) {
            'int' => (int) $value,
            'float' => (float) $value,
            'bool' => filter_var($value, FILTER_VALIDATE_BOOLEAN),
            'string' => $this->removeControlChars((string) $value),
            default => throw new \InvalidArgumentException("Unsupported parameter type: {$typeName}"),
        };
    }

    /** 移除字符串中的控制字符 */
    private function removeControlChars(string $string): string
    {
        return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $string);
    }

    /**
     * 处理应用错误
     * @param \Throwable $exception
     */
    private function handleError(\Throwable $exception): void
    {
        $statusCode = $exception instanceof \InvalidArgumentException ? 404 : 500;
        http_response_code($statusCode);

        error_log("Application Error: " . $exception->getMessage() . " in " . $exception->getFile() . ":" . $exception->getLine());

        // **职责分离**: 引入独立的错误视图
        if ($this->config['debug']) {
            $this->renderErrorView('dev_error', ['exception' => $exception, 'statusCode' => $statusCode]);
        } else {
            $this->renderErrorView('prod_error', ['statusCode' => $statusCode]);
        }
    }

    /**
     * 渲染错误视图
     * @param string $viewName 视图文件名 (不含.php)
     * @param array $data 传递给视图的数据
     */
    private function renderErrorView(string $viewName, array $data = []): void
    {
        extract($data, EXTR_SKIP);
        // 通常会有一个专门的视图路径配置
        $viewPath = dirname(__DIR__) . "/views/errors/{$viewName}.php";
        if (file_exists($viewPath)) {
            require $viewPath;
        } else {
            // 降级处理
            echo "<h1>Error {$statusCode}</h1><p>An unexpected error occurred.</p>";
        }
    }
}
